/*
 * ConnectSocketChannel.h
 *
 *  Created on: Aug 19, 2016
 *      Author: Lucifer
 */

#ifndef BFX_CONNECTSOCKETCHANNEL_H_
#define BFX_CONNECTSOCKETCHANNEL_H_

#include "./SelectableChannel.h"

namespace BFX {

class ConnectSocketChannel: public SelectableChannel {
public:
	ConnectSocketChannel() {
		_state = ST_UNCONNECTED;
	}
	virtual ~ConnectSocketChannel() {
	}

	/// @copydoc SelectableChannel::getValidOps()
	virtual int getValidOps() const {
		return (SelectionKey::OP_CONNECT | SelectionKey::OP_READ | SelectionKey::OP_WRITE);
	}
	/// @copydoc SelectableChannel::setReadyOps()
	virtual bool setReadyOps(int ops, SelectionKey* key) {
		BFX_ASSERT(key);

		int intOps = key->getInterestOps();
		int oldOps = key->getReadyOps();
		int newOps = 0;

		if ((intOps & SelectionKey::OP_CONNECT) != 0 && !isConnected())
			newOps |= SelectionKey::OP_CONNECT;
		if ((intOps & SelectionKey::OP_READ) != 0 && isConnected())
			newOps |= SelectionKey::OP_READ;
		if ((intOps & SelectionKey::OP_WRITE) != 0 && isConnected())
			newOps |= SelectionKey::OP_WRITE;

		key->setReadyOps(newOps);
		return (newOps & ~oldOps) != 0;
	}
	/// @copydoc SelectableChannel::getDescriptor()
	virtual fildes_t getDescriptor() const {
		return (_socket == nullptr) ? INVALID_FILDES : _socket->getDescriptor();
	}

	/// Gets a socket associated with this channel.
	Socket* getSocket() const {
		return _socket;
	}

	bool attach(Socket* socket) {
		BFX_ASSERT(socket && socket->isValid());
		BFX_ASSERT(!isClosed());
		BFX_ASSERT(!isConnected());

		if (socket == nullptr || !socket->isValid()) {
			SocketError::set(SocketError::EC_InvalidArgument);
			return false;
		}
		if (isConnected() || isClosed()) {
			// Already connected or closed.
			SocketError::set(SocketError::EC_IllegalStateError);
			return false;
		}

		_socket = socket;
		_state = ST_CONNECTED;
		SocketError::clear();
		return true;
	}

	bool connect(InetAddress* addr) {
		BFX_ASSERT(addr && addr->isValid());
		BFX_ASSERT(!isClosed());
		BFX_ASSERT(!isConnected());

		if (addr && addr->isValid()) {
			SocketError::set(SocketError::EC_InvalidArgument);
			return false;
		}
		if (isConnected() || isClosed()) {
			// Already connected or closed.
			SocketError::set(SocketError::EC_IllegalStateError);
			return false;
		}

		REF<Socket> socket = new Socket();
		if (!socket->connect(addr)) {
			return false;
		}

		_socket = socket;
		_state = ST_CONNECTED;
		SocketError::clear();
		return true;
	}

	bool isConnected() const {
		return (_state == ST_CONNECTED);
	}

	int read(uint8_t* dsts, int offset, int length) {
		BFX_ASSERT(!isClosed());
		BFX_ASSERT(isConnected());

		BFX_ASSERT(dsts || length > 0);
		BFX_ASSERT(offset >= 0);

		ssize_t retval;
		do {
			retval = _socket->receive(dsts + offset, length);
			if (retval < 0) {
				if (SocketError::get() == SocketError::EC_Interrupted) {
					// System call interrupted
					if (!isClosed()) {
						continue;
					}
				}
			}
		} while (0);
		return retval;
	}
	int write(const uint8_t* srcs, int offset, int length) {
		BFX_ASSERT(!isClosed());
		BFX_ASSERT(isConnected());

		BFX_ASSERT(srcs || length > 0);
		BFX_ASSERT(offset >= 0);

		ssize_t retval;
		do {
			retval = _socket->send(srcs + offset, length);
			if (retval < 0) {
				if (SocketError::get() == SocketError::EC_Interrupted) {
					if (!isClosed())
						continue;
				}
			}
		} while (0);
		return retval;
	}

private:
	// State, increases monotonically
	enum {
		ST_UNCONNECTED = 0,	///
		ST_CONNECTED = 1,	///
		ST_KILLED = 2	///
	} _state;
	// Socket adaptor, created on attach() or connect().
	REF<Socket> _socket;
};

} /* namespace BFX */

#endif /* BFX_CONNECTSOCKETCHANNEL_H_ */
